#include <QTextCodec>
#include <QtNetwork>
#include <QDomDocument>
#include <QTextEdit>
#include <qwebkitversion.h>

#include "appfactory.h"
#include "rssfactory.h"
#include "definitions.h"
#include "rssfeed.h"


RssgTypes::FeedType RssFactory::qstrToFeedType(const QString &string) {
    if (string == "RSS") return RssgTypes::FEED_RSS;
    if (string == "ATOM") return RssgTypes::FEED_ATOM;
    return RssgTypes::FEED_UNKNOWN;
}

QString RssFactory::feedTypeToQstring(const RssgTypes::FeedType &type) {
    switch (type) {
        case RssgTypes::FEED_RSS:
            return "RSS";
        case RssgTypes::FEED_ATOM:
            return "ATOM";
        default:
            return "UNKNOWN";
    }
}

RssgTypes::FeedType RssFactory::intToFeedType(int value) {
    switch (value) {
        case RssgTypes::FEED_RSS:
            return RssgTypes::FEED_RSS;
        case RssgTypes::FEED_ATOM:
            return RssgTypes::FEED_ATOM;
        default:
            return RssgTypes::FEED_UNKNOWN;
    }
}

bool RssFactory::isValid(QList<QString> list) {
    try {
        foreach (QString item, list)
            if (item.size() < LIMIT_VALID) {
                return false;
            }
    }
    catch (std::exception &e) {
        return false;
    }

    return true;
}

bool RssFactory::qstrToBool(const QString &value) {
    if (value == "true" || value == "True") {
        return true;
    }
    else {
        return false;
    }
}

QNetworkReply::NetworkError RssFactory::downloadFileToQString(const QString &url,
															  QByteArray *output,
															  int timeout,
															  bool auth,
															  QList<QString> data) {
	// A wacky way how to make asynchronous network calls
    // synchronous. The dedicated event loop is here to
    // ensure QtNetwork signals and slots behave like
    // synchronous curl-like call.
    QNetworkAccessManager manager;
    QEventLoop loop;
    QTimer timer;
    QNetworkRequest request;
    QNetworkReply *reply;

	request.setUrl(url);
	request.setRawHeader("User-Agent", APP_USERAGENT);

	if (auth)
		request.setRawHeader("Authorization", "Basic " + QByteArray(QString("%1:%2").arg(data.at(0), data.at(1)).toAscii()).toBase64());


    QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));

    while (true) {
        timer.setSingleShot(true);
        reply = manager.get(request);
        timer.start(timeout);
        loop.exec();

        if (timer.isActive())
			// Download complete.
            timer.stop();
        else
            return QNetworkReply::TimeoutError;

		// No redirections at this stage.
        if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl().isValid())
            break;
		// QUrl indicates redirection, so redirect.
        else
            request.setUrl(reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl());
    };

    *output = reply->readAll();

    QNetworkReply::NetworkError ret = reply->error();
    if (ret != QNetworkReply::NoError)
		qWarning() << QString("%1 File %2 fetched with error. %3").arg(APP_ID, url, reply->errorString());
    else
		qDebug() << QString("%1 File %2 fetched with code %3, timeout %4 miliseconds.").arg(APP_ID, url, QString::number(reply->error()), QString::number(timeout));

    reply->deleteLater();
    return ret;
}

void RssFactory::parseFeed(RssFeed *feed, int timeout) {
    QByteArray istr;
	if (feed->getUsername().size()) {
		QList<QString> auths;
		auths.append(feed->getUsername()); auths.append(feed->getPassword());
		feed->setState(RssFactory::downloadFileToQString(feed->getLink(), &istr,
														 timeout, true, auths));
	}
	else
		feed->setState(RssFactory::downloadFileToQString(feed->getLink(), &istr, timeout));
	QTextCodec *codec = QTextCodec::codecForName(feed->getEncoding().toUtf8());
    QString str = codec->toUnicode(istr);

    QDomDocument document;
    document.setContent(str, true);
    if (document.documentElement().tagName() != "rss" && document.documentElement().tagName() != "feed" && document.documentElement().tagName() != "RDF") {
		feed->setState(QNetworkReply::UnknownNetworkError);
		return;
    }

	switch (feed->getType()) {
        case RssgTypes::FEED_RSS:
			parseAsRss(str, feed);
            break;
        case RssgTypes::FEED_ATOM:
			parseAsAtom(str, feed);
            break;
        default:
            break;
    }
}

QString RssFactory::parseDate(const QString &dateString) {
    QDateTime dt;
    QString temp;
    QLocale locale(QLocale::C);
    QString date = QString(dateString).remove(QRegExp("(\n|\r|\t)"));

    if (date.isEmpty())
        return locale.toString(QDateTime::currentDateTime(), FEED_DATE_FORMAT);

    temp = date.left(19);
    dt = locale.toDateTime(temp, "yyyy-MM-ddTHH:mm:ss");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);

    temp = date.left(25);
    dt = locale.toDateTime(temp, "ddd, dd MMM yyyy HH:mm:ss");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);

    temp = date.left(date.lastIndexOf(' '));
    dt = locale.toDateTime(temp, "d MMM yyyy HH:mm:ss");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);

    temp = date.left(11);
    dt = locale.toDateTime(temp, "dd MMM yyyy");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);

    temp = date;
    dt = locale.toDateTime(temp, "yyyy-MM-dd HH:mm:ss.z");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);

    dt = locale.toDateTime(temp, "yyyy-MM-dd");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);

    temp = date;
    if (70 < date.mid(13, 2).toInt()) temp = temp.insert(13, "19");
    else temp = temp.insert(12, "20");
    temp = temp.left(25);
    dt = locale.toDateTime(temp, "ddd, dd MMM yyyy HH:mm:ss");
    if (dt.isValid()) return locale.toString(dt, FEED_DATE_FORMAT);


	qWarning() << QString("%1 Problem with parsing date %2, using current date and time.").arg(APP_ID,
																							   date);
    return locale.toString(QDateTime::currentDateTime(), FEED_DATE_FORMAT);
}

void RssFactory::parseAsRss(const QString &array, RssFeed *feed) {
    QDomDocument document;
    document.setContent(array, true);
    QDomNodeList list = document.elementsByTagName("item");

    for (int i = 0; i < list.count(); i++) {
		if (!isntInRssFeed(list.at(i).toElement(), feed)) continue;

        QString date;
        QStringList dateTags; dateTags << "pubDate" << "date";
        foreach (QString tag, dateTags) {
            if (!list.at(i).namedItem(tag).toElement().isNull()) {
                date = list.at(i).namedItem(tag).toElement().text();
                break;
            }
        }

		QString newDate = parseDate(date);

		feed->addMessage(AppFactory::toPlainText(list.at(i).namedItem("title").toElement().text()),
						list.at(i).namedItem("link").toElement().text(),
						list.at(i).namedItem("description").toElement().text(),
						newDate);
    }
}

bool RssFactory::isntInRssFeed(const QDomElement &node, RssFeed *feed) {
	if ((node.namedItem("title").toElement().isNull() &&
		 node.namedItem("description").toElement().isNull()) || node.namedItem("link").toElement().isNull())
		return false;
	return feed->getItemByLink(node.namedItem("link").toElement().text()) == NULL ?
				true :
				false;
}

void RssFactory::parseAsAtom(const QString &array, RssFeed *feed) {
    QDomDocument document;
    document.setContent(array, true);
    QDomNodeList list = document.elementsByTagName("entry");

    for (int i = 0; i < list.count(); i++) {

		if (!isntInAtomFeed(list.at(i).toElement(), feed)) continue;

        QString date;
        QStringList dateTags; dateTags << "updated" << "published";
        foreach (QString tag, dateTags) {
            if (!list.at(i).namedItem(tag).toElement().isNull()) {
                date = list.at(i).namedItem(tag).toElement().text();
                break;
            }
        }

        QString newDate = parseDate(date);

		feed->addMessage(AppFactory::toPlainText(list.at(i).namedItem("title").toElement().text()),
						list.at(i).namedItem("link").toElement().isNull() ? list.at(i).namedItem("id").toElement().text() : list.at(i).namedItem("link").toElement().attribute("href"),
						list.at(i).namedItem("content").toElement().isNull() ? list.at(i).namedItem("summary").toElement().text() : list.at(i).namedItem("content").toElement().text(),
						newDate);

    }

}

bool RssFactory::isntInAtomFeed(const QDomElement &node, RssFeed *feed) {
	if (node.namedItem("title").toElement().isNull() || (node.namedItem("id").toElement().isNull() && node.namedItem("link").toElement().isNull()))
		return false;
	if (node.namedItem("content").toElement().isNull() && node.namedItem("summary").toElement().isNull())
		return false;
	return feed->getItemByLink(node.namedItem("link").toElement().isNull() ? node.namedItem("id").toElement().text() : node.namedItem("link").toElement().attribute("href")) == NULL ?
				true :
				false;
}

RssgTypes::FeedType RssFactory::guessFeed(const QString &link, QString *out, bool auth, QList<QString> data) {
    QByteArray iarray;
    QString enc;
	// Here we use DEF_TIMEOUT (not value from settings), because guessed feed can be distant.
	QNetworkReply::NetworkError res = RssFactory::downloadFileToQString(link, &iarray, DEF_TIMEOUT,
																		auth, data);

    if (res != QNetworkReply::NoError) return RssgTypes::FEED_UNKNOWN;
    out->clear();

    QXmlStreamReader xml(iarray);

    while (!xml.atEnd()) {
        if (xml.isStartDocument()) {
            enc = xml.documentEncoding().toString();
            out->push_back(enc + ',');
            break;
        }
        xml.readNext();
    }

    QTextCodec *codec = QTextCodec::codecForName(enc.toLocal8Bit());
    QString str;
    if (codec)
        str = codec->toUnicode(iarray);
    else
        str = iarray;

    QDomDocument document;
    document.setContent(str, true);
    if (document.documentElement().tagName() == "feed") {
        out->push_back(document.documentElement().namedItem("title").toElement().text());
        return RssgTypes::FEED_ATOM;
    }
    else if (document.documentElement().tagName() == "rss" || document.documentElement().tagName() == "RDF") {
        out->push_back(document.documentElement().namedItem("channel").toElement().namedItem("title").toElement().text());
        return RssgTypes::FEED_RSS;
    }
    return RssgTypes::FEED_UNKNOWN;
}

